package main

import (
	"encoding/json"
	"fmt"
	"io"
	"time"

	"github.com/ldlw/wfrp/src/models/consts"
	"github.com/ldlw/wfrp/src/models/msg"
	"github.com/ldlw/wfrp/src/models/server"
	"github.com/ldlw/wfrp/src/utils/conn"
	"github.com/ldlw/wfrp/src/utils/log"
)

func ProcessControlConn(l *conn.Listener) {
	for {
		// 接收新的控制连接：通过循环调用 l.GetConn()，从监听器中获取新的连接，然后启动一个独立的goroutine来处理该连接
		c, err := l.GetConn()
		if err != nil {
			return
		}
		log.Debug("Get one new conn, %v", c.GetRemoteAddr())
		go controlWorker(c)
	}
}

// connection from every client and server
// 代理服务器与客户端之间的控制连接交互过程，包括接收并解析客户端发来的控制请求，判断请求是否合法，并向客户端发送响应
// 同时也负责将客户端发来的控制消息转发给代理服务器处理用户连接
func controlWorker(c *conn.Conn) {
	// the first message is from client to server
	// if error, close connection
	res, err := c.ReadLine()
	if err != nil {
		log.Warn("Read error, %v", err)
		return
	}
	log.Debug("get: %s", res)

	clientCtlReq := &msg.ClientCtlReq{}
	clientCtlRes := &msg.ClientCtlRes{}
	// 解析客户端发来的控制请求：当有新的客户端连接到代理服务器时，首先会发送一个控制请求，包含了客户端的名称、密码和要访问的代理服务器名称等信息
	// 在这里，代码通过 json.Unmarshal 将请求解析成一个 msg.ClientCtlReq对象
	if err := json.Unmarshal([]byte(res), &clientCtlReq); err != nil {
		log.Warn("Parse err: %v : %s", err, res)
		return
	}

	// check 检查控制请求：检查该请求是否符合代理服务器的要求
	succ, info, needRes := checkProxy(clientCtlReq, c)
	if !succ {
		clientCtlRes.Code = 1
		clientCtlRes.Msg = info
	}

	if needRes {
		defer c.Close()
		// 发送控制响应：如果需要向客户端发送控制响应（即前面的检查结果为 true），则将响应以 JSON 字符串的形式输出到客户端
		buf, _ := json.Marshal(clientCtlRes)
		err = c.Write(string(buf) + "\n")
		if err != nil {
			log.Warn("Write error, %v", err)
			time.Sleep(1 * time.Second)
			return
		}
	} else {
		// work conn, just return
		return
	}

	// other messages is from server to client
	s, ok := server.ProxyServers[clientCtlReq.ProxyName]
	if !ok {
		log.Warn("ProxyName [%s] is not exist", clientCtlReq.ProxyName)
		return
	}

	// read control msg from client
	// 转发控制消息：在代理服务器与客户端之间建立的控制连接上，客户端可以向代理服务器发送控制消息，以控制代理服务器的行为
	// 这些控制消息被转发到代理服务器，然后由代理服务器处理用户连接
	go readControlMsgFromClient(s, c)

	serverCtlReq := &msg.ClientCtlReq{}
	serverCtlReq.Type = consts.WorkConn
	for {
		closeFlag := s.WaitUserConn()
		if closeFlag {
			log.Debug("ProxyName [%s], goroutine for dealing user conn is closed", s.Name)
			break
		}
		buf, _ := json.Marshal(serverCtlReq)
		err = c.Write(string(buf) + "\n")
		if err != nil {
			log.Warn("ProxyName [%s], write to client error, proxy exit", s.Name)
			s.Close()
			return
		}

		log.Debug("ProxyName [%s], write to client to add work conn success", s.Name)
	}

	log.Info("ProxyName [%s], I'm dead!", s.Name)
	return
}

/*
  * succ 表示检查结果是否成功
	* info 存储检查结果的信息
	* needRes 表示是否需要返回响应给客户端
  * 检查代理请求的合法性，并根据请求类型执行相应的操作。如果检查失败，则返回相应的错误信息给客户端
*/
func checkProxy(req *msg.ClientCtlReq, c *conn.Conn) (succ bool, info string, needRes bool) {
	succ = false
	needRes = true
	// check if proxy name exist
	// 检查请求中的 ProxyName 是否存在于 server.ProxyServers 中。如果不存在，则将错误信息记录到 info 中，记录警告日志并返回
	s, ok := server.ProxyServers[req.ProxyName]
	if !ok {
		info = fmt.Sprintf("ProxyName [%s] is not exist", req.ProxyName)
		log.Warn(info)
		return
	}

	// check password
	// 检查请求中的密码是否正确。如果密码不正确，则将错误信息记录到 info 中，记录警告日志并返回
	if req.Passwd != s.Passwd {
		info = fmt.Sprintf("ProxyName [%s], password is not correct", req.ProxyName)
		log.Warn(info)
		return
	}

	// control conn
	// 如果请求类型为 consts.CtlConn，表示控制连接请求
	if req.Type == consts.CtlConn {
		// 代理服务器的状态不是 consts.Idle，表示代理服务器已经在使用中，将错误信息记录到 info 中，记录警告日志并返回
		if s.Status != consts.Idle {
			info = fmt.Sprintf("ProxyName [%s], already in use", req.ProxyName)
			log.Warn(info)
			return
		}

		// start proxy and listen for user conn, no block
		// 调用 s.Start() 启动代理服务器，并监听用户连接，非阻塞方式
		err := s.Start()
		if err != nil {
			info = fmt.Sprintf("ProxyName [%s], start proxy error: %v", req.ProxyName, err.Error())
			log.Warn(info)
			return
		}
		// 否则，记录启动成功的日志信息
		log.Info("ProxyName [%s], start proxy success", req.ProxyName)
	} else if req.Type == consts.WorkConn { // 表示工作连接请求，进行以下操作
		// work conn
		// 表示不需要返回响应给客户端
		needRes = false
		// 如果代理服务器的状态不是 consts.Working，表示代理服务器当前不在工作状态，记录警告日志并返回
		if s.Status != consts.Working {
			log.Warn("ProxyName [%s], is not working when it gets one new work conn", req.ProxyName)
			return
		}
		// 将新的客户端连接添加到代理服务器的连接列表中
		s.GetNewCliConn(c)
	} else {
		// 如果请求类型不是 consts.CtlConn 或 consts.WorkConn，表示请求类型不支持，将错误信息记录到 info 中，记录警告日志并返回
		info = fmt.Sprintf("ProxyName [%s], type [%d] unsupport", req.ProxyName, req.Type)
		log.Warn(info)
		return
	}

	// 最后，将 succ 设置为 true，并返回检查结果
	succ = true
	return
}

func readControlMsgFromClient(s *server.ProxyServer, c *conn.Conn) {
	// 是否继续读取控制消息的循环
	isContinueRead := true
	f := func() {
		isContinueRead = false
		s.Close()
		log.Error("ProxyName [%s], client heartbeat timeout", s.Name)
	}
	// 指定时间后执行函数 f
	timer := time.AfterFunc(time.Duration(server.HeartBeatTimeout)*time.Second, f)
	defer timer.Stop()

	for isContinueRead {
		// 从客户端连接中读取一行内容
		content, err := c.ReadLine()
		if err != nil {
			// io.EOF，表示客户端连接已断开，记录警告日志，关闭代理服务器连接，并跳出循环
			if err == io.EOF {
				log.Warn("ProxyName [%s], client is dead!", s.Name)
				s.Close()
				break
			} else if nil == c || c.IsClosed() {
				// c 或者客户端连接已关闭（c.IsClosed() 返回 true），记录警告日志，并跳出循环
				log.Warn("ProxyName [%s], client connection is closed", s.Name)
				break
			}

			log.Error("ProxyName [%s], read error: %v", s.Name, err)
			continue
		}

		clientCtlReq := &msg.ClientCtlReq{}
		// 如果读取到的内容能够成功解析为 msg.ClientCtlReq 结构体对象，则进一步判断该对象的 Type 字段是否为心跳请求（consts.CSHeartBeatReq）
		if err := json.Unmarshal([]byte(content), clientCtlReq); err != nil {
			log.Warn("Parse err: %v : %s", err, content)
			continue
		}
		// 如果是心跳请求，重置定时器，并构造一个心跳响应 msg.ClientCtlRes 对象，将其序列化为 JSON 字符串，并通过 c.Write 发送给客户端
		if consts.CSHeartBeatReq == clientCtlReq.Type {
			log.Debug("ProxyName [%s], get heartbeat", s.Name)
			timer.Reset(time.Duration(server.HeartBeatTimeout) * time.Second)

			clientCtlRes := &msg.ClientCtlRes{}
			clientCtlRes.GeneralRes.Code = consts.SCHeartBeatRes
			response, err := json.Marshal(clientCtlRes)
			if err != nil {
				log.Warn("Serialize ClientCtlRes err! err: %v", err)
				continue
			}

			err = c.Write(string(response) + "\n")
			if err != nil {
				log.Error("Send heartbeat response to client failed! Err:%v", err)
				continue
			}
		}
	}
}
